package com.atguigu.tingshu.common.aspect;

import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.common.anno.GuiGuLogin;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.model.user.UserInfo;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Component
@Aspect             // 该注解就是声明当前的这个类就是一个切面
@Slf4j
public class GuiguLoginAspect {

    @Autowired
    private RedisTemplate<String , String> redisTemplate ;

    /**
     * 前端传递token的时候是通过请求头进行传递
     */
    @Around("@annotation(guiGuLogin)")
    public Object around(ProceedingJoinPoint proceedingJoinPoint , GuiGuLogin guiGuLogin){

       // 获取HttpServletReqeust对象  -----> 通过RequestContextHolder
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();    // ServletRequestAttributes封装了HttpServletReqeust和HttpServleResponse
        HttpServletRequest httpServletRequest = requestAttributes.getRequest();
        String token = httpServletRequest.getHeader("token");

        boolean required = guiGuLogin.required();
        if(required) {      // 要求用户必须登录才可以访问目标接口

            if(StringUtils.isEmpty(token)) {
                throw new GuiguException(ResultCodeEnum.LOGIN_AUTH) ;
            }

            // 验证token
            String userLoginKey = RedisConstant.USER_LOGIN_KEY_PREFIX + token ;
            String userInfoJSON = redisTemplate.opsForValue().get(userLoginKey);
            if(StringUtils.isEmpty(userInfoJSON)) {     // token 不合法
                throw new GuiguException(ResultCodeEnum.LOGIN_AUTH) ;
            }

            // token合法 , 把用户的数据存储到ThreadLocal中
            UserInfo userInfo = JSON.parseObject(userInfoJSON, UserInfo.class);
            AuthContextHolder.setUserId(userInfo.getId());
            AuthContextHolder.setUsername(userInfo.getNickname());

        }else {     // 用户登录或者未登录都可以访问目标接口

            if(!StringUtils.isEmpty(token)) {

                // 验证token
                String userLoginKey = RedisConstant.USER_LOGIN_KEY_PREFIX + token ;
                String userInfoJSON = redisTemplate.opsForValue().get(userLoginKey);
                if(!StringUtils.isEmpty(userInfoJSON)) {     // token 不合法

                    // token合法 , 把用户的数据存储到ThreadLocal中
                    UserInfo userInfo = JSON.parseObject(userInfoJSON, UserInfo.class);
                    AuthContextHolder.setUserId(userInfo.getId());
                    AuthContextHolder.setUsername(userInfo.getNickname());

                }

            }

        }

        // 业务代码
        Object proceed = null;
        try {
            proceed = proceedingJoinPoint.proceed();
            return proceed ;// 返回目标接口方法的执行结果
        } catch (Throwable e) {
            log.info("try..中的代码执行了.....");
            if(e instanceof  GuiguException) {
                GuiguException guiguException = (GuiguException) e;
                throw  guiguException ;
            }else {
                throw new RuntimeException(e);
            }

        } finally {
            log.info("finally 中的代码执行了.....");
            AuthContextHolder.removeUserId();
            AuthContextHolder.removeUsername();
        }

        /**
         * 简单介绍一下ThreadLocal？        就是一个线程工具类，通过该线程工具类可以在一个线程内实现数据共享
         * ThreadLocal的底层原理是什么？     就是ThreadLocal中维护了一个Map, Map的键就是当前线程对象，值就是要共享的数据！
         * 使用ThreadLocal的时候会存在的问题？
         * 1、内存泄漏: 内存使用完毕以后没有及时释放，到时其他的程序页无法使用该内存空间
         * 2、内存溢出: 内存不够使用了产生的错误
         */

    }


}
